/*
 * Basic component library.
 *
 * Copyright (c) 1993-2010 Leo Spiekman, Xaveer Leijtens
 *
 * This file is part of libMask.
 *
 * libMask is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * libMask is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with libMask.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "mask.h"

/* Maximum number of point in a polyline or polygon */
#define MAXPTS	    (200)

void rect(double l, double w, int p1, int p2)
{
  if (mode & POLYGON)
    block(l, w, p1, p2);
  else
    polyline(l, w, p1, p2);
}

/* Polygon block */
void block(double l, double w, int p1, int p2)
{
  double x[4], y[4];

  movept(l,w,p1,1);
  x[0] = cp.x;
  y[0] = cp.y;
  movept(l,w,1,7);
  x[1] = cp.x;
  y[1] = cp.y;
  movept(l,w,7,9);
  x[2] = cp.x;
  y[2] = cp.y;
  movept(l,w,9,3);
  x[3] = cp.x;
  y[3] = cp.y;
  movept(l,w,3,p2);

  mskpolygonN(clayer, x, y, 4);
}

/* Intersection point (xi, yi) of two lines that go through (x0, y0), (x1,
 * y1) and (x2, y2), (x3, y3).
 */
void intersect(double x0, double y0, double x1, double y1,
    double x2, double y2, double x3, double y3, double *xi, double *yi)
{
  double D, Dx;

  D  = (x3-x2)*(y1-y0)-(x1-x0)*(y3-y2);
  Dx = (x3-x2)*(y2-y0)-(x2-x0)*(y3-y2);
  *xi = x0 + Dx/D * (x1-x0);
  *yi = y0 + Dx/D * (y1-y0);
}

/* Function to return the left and right point to the starting point of a
 * line segment from (x1, y1) to (x2, y2) with width w.
 */
void pointdxdy(double x0, double y0, double x1, double y1, double w,
    double *dx, double *dy)
{
  double l;

  *dx = (y1 - y0);
  *dy = (x0 - x1);
  l = sqrt((*dx)*(*dx) + (*dy)*(*dy));
  *dx *= w / 2 / l;
  *dy *= w / 2 / l;
}

/* Polyline block */
void polyline(double l, double w, int p1, int p2)
{
  double xb,yb,xe,ye;

  movept(l,w,p1,4);
  xb=cp.x;
  yb=cp.y;
  movept(l,w,4,6);
  xe=cp.x;
  ye=cp.y;
  movept(l,w,6,p2);

  mskline(clayer,w,xb,yb,xe,ye);
}

void npolyline(double x[], double y[], int n, double w)
{
  int i;

  if (n > MAXPTS) {
    fprintf(stderr, "More than maximum %d points in npolyline: %d.\n",
	MAXPTS, n);
    fprintf(stderr, "Exiting...\n");
    exit(1);
  }
  mskpolystart(clayer,w);
  push_cp();
  for (i=0; i<n; i++) {
    org(x[i],y[i],0.0,NOFLIP);
    mskpolypoint(clayer,cp.x,cp.y);
  }
  pop_cp();
  mskpolyend(clayer);
}

void npolygon(double x[], double y[], int m, int n, int max)
{
  /* m and n are the indices from front and back, resp. */
  int i;

  if (m+max-n > MAXPTS-1) { /* one extra point at the end of the polygon */
    fprintf(stderr, "More than maximum %d points in npolygon: %d.\n",
	MAXPTS, m+max-n+1);
    fprintf(stderr, "Exiting...\n");
    exit(1);
  }
  mskpolygonstart(clayer);
  push_cp();
  for (i=0; i<m; i++) {
    org(x[i],y[i],0.0,NOFLIP);
    mskpolypoint(clayer,cp.x,cp.y);
  }
  for (i=n; i<max; i++) {
    org(x[i],y[i],0.0,NOFLIP);
    mskpolypoint(clayer,cp.x,cp.y);
  }
  org(x[0],y[0],0.0,NOFLIP); /* close the polygon */
  mskpolypoint(clayer,cp.x,cp.y);
  pop_cp();
  mskpolygonend(clayer);
}

/*
 * Polylines as polygons
 *
 * In order to avoid error for software checking the layouts for errors, we
 * can write all polylines as polygons. Since we have to specify the
 * outline of two or more segments that make an angle, we have to know what
 * to do with the gap between those segments at the outside of the corner.
 * In order to determine which points are on the outside of the corner we
 * use the following algorithm:
 * Given a line segment between P0 (x0,y0) and P1 (x1,y1), another point P
 * (x,y) has the following relationship to the line segment. Compute
 * (y - y0) (x1 - x0) - (x - x0) (y1 - y0). If it is less than 0 then P is
 * to the right of the line segment, if greater than 0 it is to the left,
 * if equal to 0 then it lies on the line segment.
 *
 * The routine fills an array from the start with the anticlockwise points
 * and from the end with the clockwise points, keeping an index for the
 * start and end points.
 *
 */
void polylingo(double X[], double Y[], int n, double w)
{
  int i;
  double dx[2], dy[2], lrt, d;
  double cx[8], cy[8]; /* corner points of current and next line segment */

  /* Since the maximum number of points in GDSII is 200, we need to take
   * care of this by splitting the polyline in more parts, as needed. This
   * is also done in this routine.
   */
  const double fracwidth = 0.5; /* the fraction of the width of the line
				   segments that is used to determine if a
				   single point is sufficient to describe
				   the outline, or that two points are
				   needed. */
  int i_i=0, i_f=MAXPTS-1; /* initial and final point index */
  double x[MAXPTS-1], y[MAXPTS-1];


  /* at least 2 points */
  if (n<2) {
    fprintf(stderr, "Polylingo: need at least 2 points for polyline.\n");
    exit(1);
  }
  /* Start with the first two points */
  pointdxdy(X[0], Y[0], X[1], Y[1], w, &dx[1], &dy[1]);
  cx[4] = X[0] + dx[1]; cy[4] = Y[0] + dy[1];
  cx[5] = X[0] - dx[1]; cy[5] = Y[0] - dy[1];
  cx[6] = X[1] + dx[1]; cy[6] = Y[1] + dy[1];
  cx[7] = X[1] - dx[1]; cy[7] = Y[1] - dy[1];
  x[i_i] = cx[4]; y[i_i++] = cy[4];
  x[--i_f] = cx[5]; y[i_f] = cy[5];
  for (i=1; i<n-1; i++) { /* loop over the points in the polyline */
    dx[0] = dx[1]; dy[0] = dy[1];
    pointdxdy(X[i], Y[i], X[i+1], Y[i+1], w, &dx[1], &dy[1]);
    /* Shift corner points from next to current segment. */
    cx[0] = cx[4]; cy[0] = cy[4];
    cx[1] = cx[5]; cy[1] = cy[5];
    cx[2] = cx[6]; cy[2] = cy[6];
    cx[3] = cx[7]; cy[3] = cy[7];
    /* Get corner points for next segment. */
    cx[4] = X[i]   + dx[1]; cy[4] = Y[i]   + dy[1];
    cx[5] = X[i]   - dx[1]; cy[5] = Y[i]   - dy[1];
    cx[6] = X[i+1] + dx[1]; cy[6] = Y[i+1] + dy[1];
    cx[7] = X[i+1] - dx[1]; cy[7] = Y[i+1] - dy[1];
    /* We need at least 5 remaining points: (up to) three for the current
     * connection and possibly two to end the polygon. If less, stop the
     * current polygon by adding two end points that are halfway the
     * current section, and start a new by starting with these same two
     * points.
     */
    if ((i_f-i_i) < 5) {
      cx[0] = (cx[0] + cx[2]) / 2; cy[0] = (cy[0] + cy[2]) / 2;
      cx[1] = (cx[1] + cx[3]) / 2; cy[1] = (cy[1] + cy[3]) / 2;
      x[i_i] = cx[0]; y[i_i++] = cy[0];
      x[--i_f] = cx[1]; y[i_f] = cy[1];
      npolygon(x, y, i_i, i_f, MAXPTS-1);
      i_i=0, i_f=MAXPTS-1;
      x[i_i] = cx[0]; y[i_i++] = cy[0];
      x[--i_f] = cx[1]; y[i_f] = cy[1];
    }
    /* left or right turn */
    lrt = ((Y[i+1]-Y[i-1]) * (X[i]-X[i-1]) - (X[i+1]-X[i-1]) * (Y[i]-Y[i-1]));
    /* Distance between the two points at the kink (2->4 = 3->5). */
    d = sqrt((cx[4]-cx[2])*(cx[4]-cx[2]) + (cy[4]-cy[2])*(cy[4]-cy[2]));
    /* the inside corner point is on the intersection of the two inside
     * lines.
     */
    if (lrt > 0) { /* Left turn */
      /* Outside corner: use two points, unless these points are close. */
      if (d < fracwidth*w) {
	intersect(cx[0],cy[0],cx[2],cy[2],cx[4],cy[4],cx[6],cy[6],
	    &(x[i_i]),&(y[i_i]));
	i_i++;
      } else {
	x[i_i] = X[i] + dx[0]; y[i_i++] = Y[i] + dy[0];
	x[i_i] = X[i] + dx[1]; y[i_i++] = Y[i] + dy[1];
      }
      /* Inside corner: always intersect. */
      i_f--;
      intersect(cx[1],cy[1],cx[3],cy[3],cx[5],cy[5],cx[7],cy[7],
	  &(x[i_f]),&(y[i_f]));
    } else if (lrt < 0) { /* Right turn */
      /* Outside corner: use two points, unless these points are close. */
      if (d < fracwidth*w) {
	i_f--;
	intersect(cx[1],cy[1],cx[3],cy[3],cx[5],cy[5],cx[7],cy[7],
	    &(x[i_f]),&(y[i_f]));
      } else {
	x[--i_f] = X[i] - dx[0]; y[i_f] = Y[i] - dy[0];
	x[--i_f] = X[i] - dx[1]; y[i_f] = Y[i] - dy[1];
      }
      /* Inside corner: always intersect. */
      intersect(cx[0],cy[0],cx[2],cy[2],cx[4],cy[4],cx[6],cy[6],
	  &(x[i_i]),&(y[i_i]));
      i_i++;
    } else continue; /* No turn: goto next point */
  }
  /* Last two points. */
  x[i_i] = cx[6]; y[i_i++] = cy[6];
  x[--i_f] = cx[7]; y[i_f] = cy[7];

  npolygon(x, y, i_i, i_f, MAXPTS-1);
}

